/*
 * Image.cpp
 *
 *  Created on: Jul 7, 2009
 *      Author: kurt
 */

#include "Image.h"
#include "DrawingCanvas.h"
#include "Types.h"
#include <pspgu.h>
#include <pspgum.h>
#include <malloc.h>
#include <cstring>
#include <valloc.h>
#include <sstream>
#include <math.h>
#include <psputils.h>
#include "ImageUtil.h"
using namespace std;
using namespace objdraw;

Image::PaletteData * Image::CreatePalette(int size, bool in_vram, short pixelFormat)
{
	PaletteData * palette = (Image::PaletteData *)malloc(sizeof(Image::PaletteData));
	if(palette == NULL)
		return NULL;
	palette->pixelFormat = pixelFormat;
	palette->nentries = size;
	palette->in_vram = in_vram;
	palette->size = (palette->nentries * ImageUtil::paletteSizes[pixelFormat]) >> 3;
	if(!in_vram)
		palette->data = (uint *)memalign(16,palette->size);
	else
		palette->data = (uint *)valloc(palette->size);
	if(palette->data == NULL)
	{
		free(palette);
		return NULL;
	}
	memset(palette->data,0,palette->size);
	return palette;
}

Image::~Image() 
{
	if(image != NULL)
	{
		if(image->data != NULL)
		{
			if(image->in_vram) 
				vfree(image->data);
			else 
				free(image->data);
		}
		if(image->palette != NULL)
		{
			if(image->palette->data != NULL)
			{
				if(image->palette->in_vram)
					vfree(image->palette->data);
				else
					free(image->palette->data);
			}
			free(image->palette);
		}
		free(image);
		image = NULL;
	}
}
//Image::Image() 
//{
//	start = Location(0,0);
//	end = Location(0,0);
//	color = Color(0,0,0);
//	hidden = false;
//	image = NULL;
//	canvas = NULL;
//	mirroredVert = mirroredHori = false;
//}
Image::Image(Image const & r)
{
	start = r.start;
	end = r.end;
	color = r.color;
	hidden = r.hidden;
	canvas = r.canvas;
	mirroredVert = r.mirroredVert;
	mirroredHori = r.mirroredHori;
	
	image = NULL;
	if(r.image != NULL)
	{
		image = (ImageData *)malloc(sizeof(ImageData));
		if(image == NULL) 
			return;
		memcpy(image,r.image,sizeof(ImageData));
		image->in_vram = false;
		image->data = NULL;
		image->palette = NULL;
		if(r.image->data != NULL)
		{
			int size = r.image->size;
			if(r.image->in_vram)
			{
				image->data = (unsigned int *)valloc(size);
				if(image->data != NULL)
					image->in_vram = true;
			}
			if(image->data == NULL)
				image->data = (unsigned int *)memalign(16,size);
			if(image->data != NULL)
				memcpy(image->data,r.image->data,size);
		}
		if(r.image->palette != NULL)
		{
			image->palette = (PaletteData *)malloc(sizeof(PaletteData));
			if(image->palette != NULL)
			{
				if(r.image->palette->in_vram)
					image->palette->data = (unsigned int *)valloc(sizeof(r.image->palette->size));
				else
					image->palette->data = (unsigned int *)memalign(16,sizeof(r.image->palette->size));
				if(image->palette->data != NULL)
				{
					memcpy(image->palette->data,r.image->palette->data,r.image->palette->size);
					image->palette->nentries = r.image->palette->nentries;
					image->palette->pixelFormat = r.image->palette->pixelFormat;
					image->palette->size = r.image->palette->size;
				}
			}
		}
	}
	if(canvas != NULL)
		canvas->getCanvasContent().addDrawable(this);
}
Image::Image(DrawingCanvas * c)
{
	start = Location(0,0);
	end = Location(0,0);
	color = Color(0,0,0);
	hidden = false;
	image = NULL;
	canvas = c;
	mirroredVert = mirroredHori = false;
	if(canvas != NULL)
		canvas->getCanvasContent().addDrawable(this);
}
uint Image::getPixel(uint x, uint y)
{
//	TODO get swizzled pixel address
	if(swizzled() || image == NULL || image->data == NULL)
		return 0;
	int index = (y * image->width) + x;
	if(index >= image->size)
		return 0;
	return image->data[index];
}
void Image::setPixel(uint x, uint y, uint pixel)
{
//	TODO get swizzled pixel address
	if(swizzled() || image == NULL || image->data == NULL)
		return;
	int index = (y * image->width) + x;
	if(index >= image->size)
		return;
	image->data[index] = pixel;
}
uint Image::getImageWidth()
{
	if(image != NULL && image->data != NULL)
		return image->width;
	return 0;
}
uint Image::getImageHeigth()
{
	if(image != NULL && image->data != NULL)
		return image->height;
	return 0;
}
void Image::swizzle()
{
	if(image == NULL)
		return;
	if(image->data == NULL || image->swizzled)
		return;
	
	int _alignWidth = image->textureWidth;
	int _alignHeight = image->textureHeight;
	u32 * _data = (u32 *)image->data;
	
	unsigned int swizwidth = (_alignWidth * sizeof(u32));
	u32 * temp = (u32 *)memalign(16, _alignHeight * swizwidth);
	
	if(temp == NULL) 
		return;
	
	unsigned int blockx, blocky;
	unsigned int j;
	unsigned int width_blocks = swizwidth >> 4;
	unsigned int height_blocks = _alignHeight >> 3;
	unsigned int src_pitch = (swizwidth - 16) >> 2;
	unsigned int src_row = swizwidth << 3;
	const u8* ysrc = (u8*)_data;
	u32* dst = temp;
	for(blocky = 0; blocky < height_blocks; ++blocky) 
	{
		const u8* xsrc = ysrc;
		for(blockx = 0; blockx < width_blocks; ++blockx) 
		{
			const u32* src = (u32*)xsrc;
			for(j = 0; j < 8; ++j) 
			{
				*(dst++) = *(src++);
				*(dst++) = *(src++);
				*(dst++) = *(src++);
				*(dst++) = *(src++);
				src += src_pitch;
			}
			xsrc += 16;
		}
		ysrc += src_row;
	}
	free(_data);
	image->data = temp;
	image->swizzled = true;
	sceKernelDcacheWritebackAll();
}
void Image::unswizzle()
{
	if(image == NULL)
		return;
	if(image->data == NULL || !image->swizzled)
		return;
	
	int _alignWidth = image->textureWidth;
	int _alignHeight = image->textureHeight;
	u32 * _data = (u32 *)image->data;
	
	unsigned int swizwidth = (_alignWidth * sizeof(u32));
	
	u32 * temp = (u32 *)memalign(16, swizwidth * _alignHeight);
	if(temp == NULL) 
		return;
	
	unsigned int blockx, blocky;
	unsigned int j;
	unsigned int width_blocks = swizwidth >> 4;
	unsigned int height_blocks = _alignHeight >> 3;
	unsigned int dst_pitch = (swizwidth - 16) >> 2;
	unsigned int dst_row = swizwidth << 3;
	
	const u8* ydst = (u8*)temp;
	u32* src = _data;
	
	for(blocky = 0; blocky < height_blocks; ++blocky) 
	{
		const u8* xdst = ydst;
		for(blockx = 0; blockx < width_blocks; ++blockx) 
		{
			u32* dst = (u32*)xdst;
			for(j = 0; j < 8; ++j) 
			{
				*(dst++) = *(src++);
				*(dst++) = *(src++);
				*(dst++) = *(src++);
				*(dst++) = *(src++);
				dst += dst_pitch;
			}
			xdst += 16;
		}
		ydst += dst_row;
	}
	free(_data);
	image->data = temp;
	image->swizzled = false;
	sceKernelDcacheWritebackAll();
}
bool Image::swizzled()
{
	if(image != NULL)
		return image->swizzled;
	return false;
}
bool Image::isMirroredVert()
{
	return mirroredVert;
}
bool Image::isMirroredHori()
{
	return mirroredHori;
}
void Image::mirrorVert()
{
	mirroredVert = true;
}
void Image::mirrorHori()
{
	mirroredHori = true;
}
void Image::setVertMirror(bool mir)
{
	mirroredVert = mir;
}
void Image::setHoriMirror(bool mir)
{
	mirroredHori = mir;
}
void Image::render_big()
{
	if(hidden || !(image != NULL && image->data != NULL)) 
		return;
	if(image->textureHeight < 512 && image->textureWidth < 512)
	{
		render();
		return;
	}
	
	sceGuDisable(GU_DEPTH_TEST); 
	sceGuEnable(GU_TEXTURE_2D);
	if(image->palette != NULL)
	{
		if(image->palette->data != NULL)
		{
//			upload palette
			sceGuClutMode(image->palette->pixelFormat,0,0xff,0);
			sceGuClutLoad((image->palette->nentries>>3), image->palette->data);
		}
	}
// setting the texture
	sceGuTexMode(image->pixelFormat,0,0,image->swizzled);
	sceGuTexFilter( GU_LINEAR, GU_LINEAR );
	sceGuTexFunc(GU_TFX_REPLACE, image->pixelFormat == GU_PSM_5650 ? GU_TCC_RGB : GU_TCC_RGBA);
	sceGuTexImage(0, image->textureWidth, image->textureHeight, image->textureWidth, image->data);
	
	int slice = 64;
	int sliceWidth = slice;
	int width = getWidth();
	int height = getHeight();
	int dx = start.x, dy = start.y, sx = 0, sy = 0;
	
	for(int j = 0; j < width; j += sliceWidth)
	{
		
		vertex2d * points = (vertex2d *)sceGuGetMemory(2 * sizeof(vertex2d));
		sliceWidth = slice;
		if (j + sliceWidth > width) sliceWidth = width - j;
		points[0].u = sx + j;
		points[0].v = sy;
		points[0].x = dx + j;
		points[0].y = dy;
		points[0].z = 0;
		points[0].color = 0;
		
		points[1].u = sx + j + sliceWidth;
		points[1].v = sy + height;
		points[1].x = dx + j + sliceWidth;
		points[1].y = dy + height;
		points[1].z = 0;
		points[1].color = 0;
		sceGuDrawArray(GU_SPRITES, GU_COLOR_8888 | GU_TEXTURE_32BITF | GU_VERTEX_32BITF | GU_TRANSFORM_2D, 2, 0, points);
	}
	
	// enable the depthtesting again.
	sceGuEnable(GU_DEPTH_TEST); 
	sceGuDisable(GU_TEXTURE_2D);
}
void Image::render()
{
	if(hidden || !(image != NULL && image->data != NULL)) 
		return;
	if(image->textureHeight >= 512 || image->textureWidth >= 512)
	{
		render_big();
		return;
	}
// we do not need to test for depth
	sceGuDisable(GU_DEPTH_TEST); 
	sceGuEnable(GU_TEXTURE_2D);
	if(image->palette != NULL)
	{
		if(image->palette->data != NULL)
		{
//			upload palette
			sceGuClutMode(image->palette->pixelFormat,0,0xff,0);
			sceGuClutLoad((image->palette->nentries>>3), image->palette->data);
		}
	}
// setting the texture
	sceGuTexMode(image->pixelFormat,0,0,image->swizzled);
	sceGuTexFilter( GU_LINEAR, GU_LINEAR );
	sceGuTexFunc(GU_TFX_REPLACE, image->pixelFormat == GU_PSM_5650 ? GU_TCC_RGB : GU_TCC_RGBA);
	sceGuTexImage(0, image->textureWidth, image->textureHeight, image->textureWidth, image->data);
	
	vertex2d * points = (vertex2d *)sceGuGetMemory(4 * sizeof(vertex2d));
	points[0].color = points[1].color = GU_RGBA(255,255,255,255);
	points[2].color = points[3].color = GU_RGBA(255,255,255,255);
	
	points[0].u = !mirroredVert ? 0.0f : image->textureWidth;
	points[0].v = !mirroredHori ? 0.0f : image->textureHeight;
	points[0].x = start.x;
	points[0].y = start.y;
	points[0].z = 0.0f;
	
	points[1].u = !mirroredVert ? image->textureWidth : 0.0f;
	points[1].v = !mirroredHori ? 0.0f : image->textureHeight;
	points[1].x = end.x;
	points[1].y = start.y;
	points[1].z = 0.0f;
	
	points[2].u = !mirroredVert ? 0.0f : image->textureWidth;
	points[2].v = !mirroredHori ? image->textureHeight : 0.0f;
	points[2].x = start.x;
	points[2].y = end.y;
	points[2].z = 0.0f;
	
	points[3].u = !mirroredVert ? image->textureWidth : 0.0f;
	points[3].v = !mirroredHori ? image->textureHeight : 0.0f;
	points[3].x = end.x;
	points[3].y = end.y;
	points[3].z = 0.0f;

// draw the trianglestrip with transform 2D
	sceGuDrawArray(GU_TRIANGLE_STRIP, GU_COLOR_8888 | GU_TEXTURE_32BITF | GU_VERTEX_32BITF | GU_TRANSFORM_2D, 4, 0, points);
	
// enable the depthtesting again.
	sceGuEnable(GU_DEPTH_TEST); 
	sceGuDisable(GU_TEXTURE_2D);

}
